package com.gitee.wsl.ext.string

import com.gitee.wsl.ext.string.StringExt.isAnyEmpty

/**
 *
 * Abbreviates a String using ellipses. This will turn
 * "Now is the time for all good men" into "Now is the time for..."
 *
 *
 * Specifically:
 *
 *  * If the number of characters in `str` is less than or equal to
 * `maxWidth`, return `str`.
 *  * Else abbreviate it to `(substring(str, 0, max-3) + "...")`.
 *  * If `maxWidth` is less than `4`, throw an
 * `IllegalArgumentException`.
 *  * In no case will it return a String of length greater than
 * `maxWidth`.
 *
 *
 * <pre>
 * StringUtils.abbreviate(null, *)      = null
 * StringUtils.abbreviate("", 4)        = ""
 * StringUtils.abbreviate("abcdefg", 6) = "abc..."
 * StringUtils.abbreviate("abcdefg", 7) = "abcdefg"
 * StringUtils.abbreviate("abcdefg", 8) = "abcdefg"
 * StringUtils.abbreviate("abcdefg", 4) = "a..."
 * StringUtils.abbreviate("abcdefg", 3) = IllegalArgumentException
</pre> *
 *
 * @param str  the String to check, may be null
 * @param maxWidth  maximum length of result String, must be at least 4
 * @return abbreviated String, `null` if null String input
 * @throws IllegalArgumentException if the width is too small
 * @since 2.0
 */
fun String.abbreviate(maxWidth: Int): String {
    return abbreviate( "...", 0, maxWidth)
}

/**
 *
 * Abbreviates a String using ellipses. This will turn
 * "Now is the time for all good men" into "...is the time for..."
 *
 *
 * Works like `abbreviate(String, int)`, but allows you to specify
 * a "left edge" offset.  Note that this left edge is not necessarily going to
 * be the leftmost character in the result, or the first character following the
 * ellipses, but it will appear somewhere in the result.
 *
 *
 * In no case will it return a String of length greater than
 * `maxWidth`.
 *
 * <pre>
 * StringUtils.abbreviate(null, *, *)                = null
 * StringUtils.abbreviate("", 0, 4)                  = ""
 * StringUtils.abbreviate("abcdefghijklmno", -1, 10) = "abcdefg..."
 * StringUtils.abbreviate("abcdefghijklmno", 0, 10)  = "abcdefg..."
 * StringUtils.abbreviate("abcdefghijklmno", 1, 10)  = "abcdefg..."
 * StringUtils.abbreviate("abcdefghijklmno", 4, 10)  = "abcdefg..."
 * StringUtils.abbreviate("abcdefghijklmno", 5, 10)  = "...fghi..."
 * StringUtils.abbreviate("abcdefghijklmno", 6, 10)  = "...ghij..."
 * StringUtils.abbreviate("abcdefghijklmno", 8, 10)  = "...ijklmno"
 * StringUtils.abbreviate("abcdefghijklmno", 10, 10) = "...ijklmno"
 * StringUtils.abbreviate("abcdefghijklmno", 12, 10) = "...ijklmno"
 * StringUtils.abbreviate("abcdefghij", 0, 3)        = IllegalArgumentException
 * StringUtils.abbreviate("abcdefghij", 5, 6)        = IllegalArgumentException
</pre> *
 *
 * @param str  the String to check, may be null
 * @param offset  left edge of source String
 * @param maxWidth  maximum length of result String, must be at least 4
 * @return abbreviated String, `null` if null String input
 * @throws IllegalArgumentException if the width is too small
 * @since 2.0
 */
fun String.abbreviate( offset: Int, maxWidth: Int): String {
    return abbreviate( "...", offset, maxWidth)
}

/**
 *
 * Abbreviates a String using another given String as replacement marker. This will turn
 * "Now is the time for all good men" into "Now is the time for..." if "..." was defined
 * as the replacement marker.
 *
 *
 * Specifically:
 *
 *  * If the number of characters in `str` is less than or equal to
 * `maxWidth`, return `str`.
 *  * Else abbreviate it to `(substring(str, 0, max-abbrevMarker.length) + abbrevMarker)`.
 *  * If `maxWidth` is less than `abbrevMarker.length + 1`, throw an
 * `IllegalArgumentException`.
 *  * In no case will it return a String of length greater than
 * `maxWidth`.
 *
 *
 * <pre>
 * StringUtils.abbreviate(null, "...", *)      = null
 * StringUtils.abbreviate("abcdefg", null, *)  = "abcdefg"
 * StringUtils.abbreviate("", "...", 4)        = ""
 * StringUtils.abbreviate("abcdefg", ".", 5)   = "abcd."
 * StringUtils.abbreviate("abcdefg", ".", 7)   = "abcdefg"
 * StringUtils.abbreviate("abcdefg", ".", 8)   = "abcdefg"
 * StringUtils.abbreviate("abcdefg", "..", 4)  = "ab.."
 * StringUtils.abbreviate("abcdefg", "..", 3)  = "a.."
 * StringUtils.abbreviate("abcdefg", "..", 2)  = IllegalArgumentException
 * StringUtils.abbreviate("abcdefg", "...", 3) = IllegalArgumentException
</pre> *
 *
 * @param str  the String to check, may be null
 * @param abbrevMarker  the String used as replacement marker
 * @param maxWidth  maximum length of result String, must be at least `abbrevMarker.length + 1`
 * @return abbreviated String, `null` if null String input
 * @throws IllegalArgumentException if the width is too small
 * @since 3.6
 */
fun String.abbreviate( abbrevMarker: String, maxWidth: Int): String {
    return abbreviate(abbrevMarker, 0, maxWidth)
}

/**
 *
 * Abbreviates a String using a given replacement marker. This will turn
 * "Now is the time for all good men" into "...is the time for..." if "..." was defined
 * as the replacement marker.
 *
 *
 * Works like `abbreviate(String, String, int)`, but allows you to specify
 * a "left edge" offset.  Note that this left edge is not necessarily going to
 * be the leftmost character in the result, or the first character following the
 * replacement marker, but it will appear somewhere in the result.
 *
 *
 * In no case will it return a String of length greater than `maxWidth`.
 *
 * <pre>
 * StringUtils.abbreviate(null, null, *, *)                 = null
 * StringUtils.abbreviate("abcdefghijklmno", null, *, *)    = "abcdefghijklmno"
 * StringUtils.abbreviate("", "...", 0, 4)                  = ""
 * StringUtils.abbreviate("abcdefghijklmno", "---", -1, 10) = "abcdefg---"
 * StringUtils.abbreviate("abcdefghijklmno", ",", 0, 10)    = "abcdefghi,"
 * StringUtils.abbreviate("abcdefghijklmno", ",", 1, 10)    = "abcdefghi,"
 * StringUtils.abbreviate("abcdefghijklmno", ",", 2, 10)    = "abcdefghi,"
 * StringUtils.abbreviate("abcdefghijklmno", "::", 4, 10)   = "::efghij::"
 * StringUtils.abbreviate("abcdefghijklmno", "...", 6, 10)  = "...ghij..."
 * StringUtils.abbreviate("abcdefghijklmno", "*", 9, 10)    = "*ghijklmno"
 * StringUtils.abbreviate("abcdefghijklmno", "'", 10, 10)   = "'ghijklmno"
 * StringUtils.abbreviate("abcdefghijklmno", "!", 12, 10)   = "!ghijklmno"
 * StringUtils.abbreviate("abcdefghij", "abra", 0, 4)       = IllegalArgumentException
 * StringUtils.abbreviate("abcdefghij", "...", 5, 6)        = IllegalArgumentException
</pre> *
 *
 * @param str  the String to check, may be null
 * @param abbrevMarker  the String used as replacement marker
 * @param offset  left edge of source String
 * @param maxWidth  maximum length of result String, must be at least 4
 * @return abbreviated String, `null` if null String input
 * @throws IllegalArgumentException if the width is too small
 * @since 3.6
 */
fun String.abbreviate(abbrevMarker: String, offset: Int, maxWidth: Int): String {
    val str = this
    var offset = offset
    if (str.isNotEmpty() && String.EMPTY == abbrevMarker && maxWidth > 0) {
        return str.substring( 0, maxWidth)
    } else if (isAnyEmpty(str, abbrevMarker)) {
        return str
    }
    val abbrevMarkerLength = abbrevMarker.length
    val minAbbrevWidth = abbrevMarkerLength + 1
    val minAbbrevWidthOffset = abbrevMarkerLength + abbrevMarkerLength + 1
    require(maxWidth >= minAbbrevWidth) {
        "Minimum abbreviation width is $minAbbrevWidth"
    }
    val strLen = str.length
    if (strLen <= maxWidth) {
        return str
    }
    if (offset > strLen) {
        offset = strLen
    }
    if (strLen - offset < maxWidth - abbrevMarkerLength) {
        offset = strLen - (maxWidth - abbrevMarkerLength)
    }
    if (offset <= abbrevMarkerLength + 1) {
        return str.substring(0, maxWidth - abbrevMarkerLength) + abbrevMarker
    }
    require(maxWidth >= minAbbrevWidthOffset) {
        "Minimum abbreviation width with offset is $minAbbrevWidthOffset"
    }
    return if (offset + maxWidth - abbrevMarkerLength < strLen) {
        abbrevMarker + str.substring(offset).abbreviate(abbrevMarker, maxWidth - abbrevMarkerLength)
    } else abbrevMarker + str.substring(strLen - (maxWidth - abbrevMarkerLength))
}

/**
 *
 * Abbreviates a String to the length passed, replacing the middle characters with the supplied
 * replacement String.
 *
 *
 * This abbreviation only occurs if the following criteria is met:
 *
 *  * Neither the String for abbreviation nor the replacement String are null or empty
 *  * The length to truncate to is less than the length of the supplied String
 *  * The length to truncate to is greater than 0
 *  * The abbreviated String will have enough room for the length supplied replacement String
 * and the first and last characters of the supplied String for abbreviation
 *
 *
 * Otherwise, the returned String will be the same as the supplied String for abbreviation.
 *
 *
 * <pre>
 * StringUtils.abbreviateMiddle(null, null, 0)      = null
 * StringUtils.abbreviateMiddle("abc", null, 0)      = "abc"
 * StringUtils.abbreviateMiddle("abc", ".", 0)      = "abc"
 * StringUtils.abbreviateMiddle("abc", ".", 3)      = "abc"
 * StringUtils.abbreviateMiddle("abcdef", ".", 4)     = "ab.f"
</pre> *
 *
 * @param str  the String to abbreviate, may be null
 * @param middle the String to replace the middle characters with, may be null
 * @param length the length to abbreviate `str` to.
 * @return the abbreviated String if the above criteria is met, or the original String supplied for abbreviation.
 * @since 2.5
 */
fun String.abbreviateMiddle(middle: String = ".", length: Int): String {
    val str = this
    if (isAnyEmpty(str, middle) || length >= str.length || length < middle.length + 2) {
        return str
    }
    val targetSting = length - middle.length
    val startOffset = targetSting / 2 + targetSting % 2
    val endOffset = str.length - targetSting / 2
    return str.substring(0, startOffset) +
            middle +
            str.substring(endOffset)
}

